package com.google.jessewilson.codesearch.intellij;

import ca.odell.glazedlists.EventList;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.jessewilson.codesearch.api.*;
import com.google.jessewilson.codesearch.api.spi.SearchProviderBackend;
import com.google.jessewilson.codesearch.combining.ResultsCombiner;
import com.google.jessewilson.codesearch.ui.ResultsPanelContext;
import com.google.jessewilson.codesearch.ui.ResultsPicker;
import com.google.jessewilson.codesearch.util.PushProvider;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.concurrent.ExecutorService;

/**
 * Intersect or Union the current query with another query.
 *
 * @author jessewilson
 */
class CombineQueriesAction extends AnAction {

  private final ResultsPanelContext<ResultsPanel> panelContext;

  private final QueryResponse resultsToCombineAgainst;
  private final String name;
  private final ResultsCombiner combiner;
  private final Project project;
  private final ExecutorService executorService;

  private final Provider<EventList> eventListProvider;
  private final Provider<PreflightQueryEditorDialog> preflightQueryEditorDialogProvider;
  private final Provider<IntellijResultsDisplayer> intellijResultsDisplayerProvider;

  private final PushProvider<QueryRunner> queryRunnerPushProvider;
  private final PushProvider<Project> projectPushProvider;
  private final PushProvider<Editor> editorPushProvider;

  private CombineQueriesAction(Project project,
        ResultsPanelContext<ResultsPanel> panelContext,
        QueryResponse resultsToCombineAgainst,
        String name,
        String icon,
        ResultsCombiner combiner,
        Provider<PreflightQueryEditorDialog> preflightQueryEditorDialogProvider,
        Provider<IntellijResultsDisplayer> intellijResultsDisplayerProvider,
        ExecutorService executorService,
        Provider<EventList> eventListProvider,
        PushProvider<QueryRunner> queryRunnerPushProvider,
        PushProvider<Project> projectPushProvider,
        PushProvider<Editor> editorPushProvider) {
    super(name + "...", name + "...", new ImageIcon(CombineQueriesAction.class.getClassLoader().getResource(icon)));
    this.project = project;
    this.panelContext = panelContext;
    this.resultsToCombineAgainst = resultsToCombineAgainst;
    this.name = name;
    this.combiner = combiner;
    this.preflightQueryEditorDialogProvider = preflightQueryEditorDialogProvider;
    this.intellijResultsDisplayerProvider = intellijResultsDisplayerProvider;
    this.executorService = executorService;
    this.eventListProvider = eventListProvider;
    this.queryRunnerPushProvider = queryRunnerPushProvider;
    this.projectPushProvider = projectPushProvider;
    this.editorPushProvider = editorPushProvider;
  }

  public void actionPerformed(final AnActionEvent actionEvent) {
    // build a dialog to show the query panel
    final ResultsPicker resultsPicker = new ResultsPicker(
        panelContext.getAllResults(), resultsToCombineAgainst);

    // open a dialog
    final DialogBuilder dialogBuilder = new DialogBuilder(project);
    dialogBuilder.setTitle(name);
    dialogBuilder.setCenterPanel(resultsPicker.getComponent());

    // build an action to add the combination
    AbstractAction combineAction = new AbstractAction(name) {
      public void actionPerformed(ActionEvent e) {
        QueryResponse selected = resultsPicker.getValue();

        // user didn't select a query to combine with
        if(selected == null) {
          return;
        }

        editorPushProvider.push(IntelliJUtils.editorFor(actionEvent));
        projectPushProvider.push(project);
        queryRunnerPushProvider.push(new CombinedQueryRunner());
        try {
          // provide a dialog to do a new query
          if(selected == ResultsPicker.NEW_QUERY) {
            Query query = new Query.Builder()
                .editorFile(IntelliJUtils.fileFor(actionEvent))
                .build();
            preflightQueryEditorDialogProvider.get().show(query);

          // show the results immediately
          } else {
            QueryResponse combined = combiner.combine(resultsToCombineAgainst, selected);

            // display the combined results
            intellijResultsDisplayerProvider.get().displayResults(combined);
          }

          dialogBuilder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE);

        } finally {
          editorPushProvider.pop();
          queryRunnerPushProvider.pop();
          projectPushProvider.pop();
        }
      }
    };

    // wire up the action to the dialog
    resultsPicker.setAction(combineAction);
    dialogBuilder.addAction(combineAction);
    dialogBuilder.addCancelAction();

    // show the dialog
    dialogBuilder.showModal(true);
  }

  class CombinedQueryRunner implements QueryRunner {
    public void query(Query query, SearchProviderBackend searchProviderBackend) {
      EventList<Line> newQueryResultsLines = eventListProvider.get();
      QueryResponse newQueryResponse = new DefaultQueryResponse(
          executorService, newQueryResultsLines, query, searchProviderBackend);
      newQueryResponse.getResultsPopulator().fetchMoreResults();
      QueryResponse combined = combiner.combine(resultsToCombineAgainst, newQueryResponse);

      // display the combined results
      intellijResultsDisplayerProvider.get().displayResults(combined);
    }
  }

  public static class Factory {
    @Inject private Provider<Project> projectProvider;
    @Inject private Provider<PreflightQueryEditorDialog> preflightQueryEditorDialogProvider;
    @Inject private Provider<IntellijResultsDisplayer> intellijResultsDisplayerProvider;
    @Inject private Provider<ExecutorService> executorServiceProvider;
    @Inject private Provider<EventList> eventListProvider;
    @Inject private PushProvider<QueryRunner> queryRunnerPushProvider;
    @Inject private PushProvider<Project> projectPushProvider;
    @Inject private PushProvider<Editor> editorPushProvider;
    public CombineQueriesAction create(ResultsPanelContext<ResultsPanel> resultsPanelContext, QueryResponse queryResponse, String name, String icon, ResultsCombiner resultsCombiner) {
      return new CombineQueriesAction(
          projectProvider.get(),
          resultsPanelContext,
          queryResponse,
          name,
          icon,
          resultsCombiner,
          preflightQueryEditorDialogProvider,
          intellijResultsDisplayerProvider,
          executorServiceProvider.get(),
          eventListProvider,
          queryRunnerPushProvider,
          projectPushProvider,
          editorPushProvider);
    }
  }
}
